package com.zhou.common.lib.swagger;


import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.zhou.common.lib.enums.BaseEnum;
import io.swagger.annotations.ApiModelProperty;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.schema.Annotations;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.EnumTypeDeterminer;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
import springfox.documentation.swagger.schema.ApiModelProperties;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import static springfox.documentation.schema.Collections.isContainerType;

/**
 * 展示enum的值到文档上，需实现BaseEnum类
 * public enum OnlineStatus implements BaseEnum<OnlineStatus, Integer> {
 * <p>
 * on_line(0,"在线"),off_line(1,"离线")
 * 展示为 在线状态(0-在线; 1-离线)
 */
@Slf4j
//@Import(BeanValidatorPluginsConfiguration.class)
public class EnumPropertyDisplayPlugin implements ModelPropertyBuilderPlugin {

    private final TypeResolver resolver;
    private final EnumTypeDeterminer enumTypeDeterminer;

    public EnumPropertyDisplayPlugin(TypeResolver resolver, EnumTypeDeterminer enumTypeDeterminer) {
        this.resolver = resolver;
        this.enumTypeDeterminer = enumTypeDeterminer;
    }


    @SneakyThrows
    @Override
    public void apply(ModelPropertyContext context) {

        //为枚举字段设置注释
        Optional<ApiModelProperty> annotation = Optional.empty();
        if (context.getAnnotatedElement().isPresent()) {
            annotation = Optional.ofNullable(annotation.orElseGet(
                    ApiModelProperties.findApiModePropertyAnnotation(context.getAnnotatedElement().get())::get));
        }
        if (!annotation.isPresent() && context.getBeanPropertyDefinition().isPresent()) {
            annotation = Annotations.findPropertyAnnotation(
                    context.getBeanPropertyDefinition().get(),
                    ApiModelProperty.class);
        }
        final Class<?> rawPrimaryType = context.getBeanPropertyDefinition().get().getRawPrimaryType();
        //过滤得到目标类型
        if (rawPrimaryType.isEnum()) {
            set(context, annotation, rawPrimaryType, false);
        }
        ResolvedType resolved = resolver.resolve(rawPrimaryType);
        if (isContainerType(resolved)) {
            AnnotatedField field = context.getBeanPropertyDefinition().get().getField();
            JavaType type1 = field.getType();
            JavaType contentType = type1.getContentType();
            if (contentType.isEnumType()) {
                set(context, annotation, contentType.getRawClass(), true);
            }
        }
    }

    private void set(ModelPropertyContext context, Optional<ApiModelProperty> annotation, Class<?> rawPrimaryType, Boolean isArray) throws Exception {
        Object[] enumConstants = rawPrimaryType.getEnumConstants();
        final List<String> displayValues = Arrays.stream(enumConstants).map(respCode -> respCode.toString()).collect(Collectors.toList());
        ResolvedType resolvedType = isArray ? context.getResolver().arrayType(String.class) : context.getResolver().resolve(String.class);
        Type keyType = getKeyType(rawPrimaryType);
        if (keyType != null) {
            resolvedType = isArray ? context.getResolver().arrayType(keyType) : context.getResolver().resolve(keyType);
        }
        String joinText = "(" + String.join("; ", displayValues) + ")";
        if (annotation.isPresent()) {
            joinText = annotation.get().value() + joinText;
        }
        context.getBuilder().description(joinText).type(resolvedType);
        ModelPropertyBuilder builder = context.getBuilder();
        Field allowableValues = ModelPropertyBuilder.class.getDeclaredField("allowableValues");
        allowableValues.setAccessible(true);
        allowableValues.set(builder, null);
    }


    private Type getKeyType(Class<?> clazz) {
        if (BaseEnum.class.isAssignableFrom(clazz)) {
            Type[] genericInterfaces = clazz.getGenericInterfaces();
            if (genericInterfaces.length > 0) {
                Type type = genericInterfaces[0];
                if (type instanceof ParameterizedType) {
                    ParameterizedType parameterizedType = (ParameterizedType) type;
                    Type subType = parameterizedType.getActualTypeArguments()[0];
                    return subType;
                }
            }
        }
        return null;
    }

    @Override
    public boolean supports(DocumentationType documentationType) {
        return true;
    }
}

